package com.gitee.wsl.scheduler.job.runtime.scheduler

import com.gitee.wsl.scheduler.job.bean.ScheduledJob
import com.gitee.wsl.scheduler.job.repository.JobRepository
import com.gitee.wsl.scheduler.job.runtime.JobExecutor
import com.gitee.wsl.scheduler.job.runtime.JobRegister
import com.gitee.wsl.concurrent.service.ScheduledExecutorService
import com.gitee.wsl.scheduler.job.bean.JobExecutionType
import com.gitee.wsl.scheduler.job.bean.JobStatus
import com.gitee.wsl.logger.Logger
import com.gitee.wsl.logger.debug
import com.gitee.wsl.logger.getLogger
import com.gitee.wsl.scheduler.job.repository.sample.Memory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid


internal class JobService @OptIn(ExperimentalUuidApi::class) constructor(
    executorService: ScheduledExecutorService,
    period: Long,
    private val id: Uuid,
    override val coroutineContext: CoroutineContext,
    private val jobRegister: JobRegister = JobRegister.Default,
    private val jobExecutor: JobExecutor ,
    private val jobRepository: JobRepository = JobRepository.Memory
) : SimplePeriodScheduler(executorService, period), CoroutineScope {
    private val logger = Logger.getLogger<JobService>()

    @OptIn(ExperimentalUuidApi::class)
    private suspend fun executeJob(scheduledJob: ScheduledJob) {
        val runnableJob = jobRegister.get(scheduledJob.settings.name)
        val isMyJob = jobRepository.update(scheduledJob.id, null, id, JobStatus.SCHEDULED, null, scheduledJob.retries)
        if (isMyJob) {
            jobExecutor.execute(runnableJob, scheduledJob, jobRepository)
        }
    }

    private suspend fun findAndExecuteJob(names: Set<String>): Boolean {
        if (names.isNotEmpty()) {
            return jobRepository.findNextOne(names, setOf( JobStatus.CREATED))?.let { executeJob(it) } != null
        }
        return false
    }

    private fun tryExecuteJob() {
        launch {
            var hasExecutedAJob = false
            do {
                if (jobExecutor.canExecute(JobExecutionType.BLOCKING))
                    hasExecutedAJob = findAndExecuteJob(jobRegister.jobs(JobExecutionType.BLOCKING))

                if (jobExecutor.canExecute(JobExecutionType.NON_BLOCKING))
                    hasExecutedAJob = hasExecutedAJob || findAndExecuteJob(jobRegister.jobs(JobExecutionType.NON_BLOCKING))
            } while (hasExecutedAJob)
        }
    }

    fun start()= run {
        logger.debug("Job service scheduled.")
        tryExecuteJob()
    }
}